/**
 * @file data_source.c
 *
 * @date Oct 12, 2011
 * @author seth
 *
 * @brief This file is part of FCP, created for the EBEX project
 *
 * This software is copyright (C) 2011 Columbia University
 *
 * FCP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * FCP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FCP; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>

#include <memory_util.h>
#include <log_util.h>

#include <data_file.h>
#include <raw_data.h>
#include <text_data.h>
#include <dirfile_data.h>

static data_source_interface_t process_data[5] =
{
	{ NULL },
	{
		raw_data_close,
		raw_data_open,
		raw_data_reset,
		raw_data_fillbuffer,
		raw_data_writebuffer,
		raw_data_set_parameters
	},
	{
		text_data_close,
		text_data_open,
		text_data_reset,
		text_data_fillbuffer,
		text_data_writebuffer,
		text_data_set_parameters
	},
	{
		dirfile_data_close,
		dirfile_data_open,
		dirfile_data_reset,
		dirfile_data_fillbuffer,
		dirfile_data_writebuffer,
		dirfile_data_set_parameters
	},

	{ NULL }
};

bool ebex_data_close_source(data_source_t *m_this)
{
	if (!m_this) return false;

	ebex_data_write_buffer(m_this);
	for (size_t i = 0; i < m_this->num_datafiles; i++)
	{
		process_data[m_this->datafile[i].type].file_close(&m_this->datafile[i]);
		e_free_quiet(m_this->databuffer[i]);
	}

	process_data[m_this->timefile->type].file_close(m_this->timefile);

	e_free_quiet(m_this->databuffer);
	e_free_quiet(m_this->timebuffer);
	e_free_quiet(m_this->datafile);
	e_free_quiet(m_this->timefile);
	e_free_quiet(m_this);

	return true;
}

data_source_t *ebex_data_new_source(const size_t m_buffersize)
{
	data_source_t *retval = NULL;

	retval = e_malloc(sizeof(data_source_t));
	EBEX_ZERO_P(retval);

	retval->buffer_size = m_buffersize;

	retval->timefile = e_malloc(sizeof(data_file_t));
	EBEX_ZERO_P(retval->timefile);

	retval->timebuffer = e_malloc(sizeof(double) * m_buffersize);

	return retval;
}

bool ebex_data_resize(data_source_t *m_this, const size_t m_buffersize)
{
	if (m_buffersize <= m_this->buffer_size)
	{
		elog(log_level_warning, "Cannot reduce buffer size");
		return false;
	}

	m_this->timebuffer = e_realloc(m_this->timebuffer, sizeof(double) * m_buffersize);
	for (size_t i = 0; i < m_this->num_datafiles; i++ )
	{
		m_this->databuffer[i] = e_rememalign(m_this->databuffer[i], sizeof(double) * m_buffersize);
	}
	m_this->buffer_size = m_buffersize;

	return true;
}

/**
 * Moves data to the beginning of the data buffer.
 * @param m_source Data source to re-organize
 * @return Number of elements remaining in the buffer
 */
size_t ebex_data_reorganize(data_source_t *m_source)
{
	if (m_source->buffer_consumed >= m_source->buffer_produced)
	{
		m_source->buffer_consumed = 0;
		m_source->buffer_produced = 0;
		return 0;
	}

	for (size_t i = 0; i < m_source->num_datafiles; i++)
	{
		memmove(m_source->databuffer[i], m_source->databuffer[i] + m_source->buffer_consumed,
				(m_source->buffer_produced - m_source->buffer_consumed) * sizeof(double));
	}
	memmove(m_source->timebuffer, m_source->timebuffer + m_source->buffer_consumed, (m_source->buffer_produced - m_source->buffer_consumed) * sizeof(double));

	m_source->buffer_produced -= m_source->buffer_consumed;
	m_source->buffer_consumed = 0;

	return m_source->buffer_produced;
}

bool ebex_data_reset_source(data_source_t *m_source)
{
	for (size_t i = 0; i < m_source->num_datafiles; i++)
	{
		if (!process_data[m_source->datafile[i].type].file_reset(&m_source->datafile[i])) return false;
	}
	if (!process_data[m_source->timefile->type].file_reset(m_source->timefile)) return false;

	m_source->buffer_consumed = m_source->buffer_produced = 0;
	return true;
}
data_file_t *ebex_data_add_datafile(data_source_t *m_source, const char *m_filename, data_file_types_t m_type, size_t m_offset, bool m_forwriting)
{
	size_t numdatafiles;
	if (!m_filename)
	{
		elog(log_level_normal, "Missing filename!");
		return NULL;
	}

	if (m_type == DATA_FILE_NONE || m_type > DATA_FILE_NETCDF)
	{
		elog(log_level_normal, "Bad data type %d for %s", (int)m_type, m_filename);
		return NULL;
	}

	numdatafiles = ++m_source->num_datafiles;

	m_source->datafile = e_realloc(m_source->datafile, numdatafiles * sizeof(data_file_t));
	EBEX_ZERO_P(&(m_source->datafile[numdatafiles - 1]));
	if (m_forwriting) m_source->datafile[numdatafiles - 1].is_sink = true;

	m_source->databuffer = e_realloc(m_source->databuffer, numdatafiles * sizeof(double*));
	m_source->databuffer[numdatafiles - 1] = e_memalign(m_source->buffer_size * sizeof(double));

	if (process_data[m_type].file_open(&(m_source->datafile[numdatafiles - 1]), m_filename, m_offset))
		return &(m_source->datafile[numdatafiles - 1]);

	dief("Could not open %s", m_filename);
	return NULL;
}

data_file_t *ebex_data_set_timefile(data_source_t *m_source, const char *m_filename, data_file_types_t m_type, size_t m_offset, bool m_forwriting)
{
	if (!m_filename)
	{
		elog(log_level_normal, "Missing filename!");
		return NULL;
	}

	if (m_type == DATA_FILE_NONE || m_type > DATA_FILE_NETCDF)
	{
		elog(log_level_normal, "Bad data type %d for %s", (int)m_type, m_filename);
		return NULL;
	}

	if (m_forwriting) m_source->timefile->is_sink = true;
	if(process_data[m_type].file_open(m_source->timefile, m_filename, m_offset))
		return m_source->timefile;

	dief("Could not open %s", m_filename);
	return NULL;
}

size_t ebex_data_fill_buffer(data_source_t *m_source, size_t m_num)
{
	size_t retval = 0;
	size_t desired_size;

	/**
	 * If we don't have enough room in our buffer for the desired items, we shuffle data to the front.
	 * This routine merely ensures that the buffer is completely filled.  The extra check allows us to
	 * avoid reorganization if we don't need it.
	 */
	if (m_num > m_source->buffer_size - m_source->buffer_consumed)
	{
		ebex_data_reorganize(m_source);
	}

	desired_size = m_source->buffer_size - m_source->buffer_produced;
	retval = process_data[m_source->timefile->type].fill_buffer(m_source->timefile, m_source->timebuffer + m_source->buffer_produced, desired_size);

	if (retval != desired_size)
	{
		elog(log_level_normal, "Wanted %zu elements from file %s, got %zu", desired_size, m_source->timefile->filename, retval);
		desired_size = retval;
	}

	for (size_t i = 0; i < m_source->num_datafiles; i++)
	{
		if (!desired_size) break;

		retval = process_data[m_source->datafile[i].type].fill_buffer(&m_source->datafile[i], m_source->databuffer[i] + m_source->buffer_produced, desired_size);
		if (retval != desired_size)
		{
			elog(log_level_normal, "Wanted %zu elements from file %s, got %zu", desired_size, m_source->datafile[i].filename, retval);
			desired_size = retval;
		}
	}

	m_source->buffer_produced += retval;
	return retval;
}

size_t ebex_data_write_buffer(data_source_t *m_source)
{
	size_t retval = 0;
	size_t desired_size;

	desired_size = m_source->buffer_produced - m_source->buffer_consumed;
	retval = process_data[m_source->timefile->type].write_buffer(m_source->timefile, m_source->timebuffer + m_source->buffer_consumed, desired_size);
	if (retval != desired_size)
	{
		elog(log_level_fatal, "Wanted to write %zu bytes to file %s, but only got %zu", desired_size, m_source->timefile->filename, retval);
		return 0;
	}

	for (size_t i = 0; i < m_source->num_datafiles; i++)
	{
		retval = process_data[m_source->datafile[i].type].write_buffer(&m_source->datafile[i], m_source->databuffer[i] + m_source->buffer_consumed, desired_size);
		if (retval != desired_size)
		{
			elog(log_level_fatal, "Wanted to write %zu bytes to file %s, but only got %zu", desired_size, m_source->datafile[i].filename, retval);
			desired_size = retval;
		}
	}

	m_source->buffer_consumed += retval;
	ebex_data_reorganize(m_source);

	return retval;
}

size_t ebex_data_copy_to_buffer_by_dirfile_field(data_source_t *m_source, const char *m_buffer, const double *m_data, size_t m_num)
{
	for (size_t i = 0; i < m_source->num_datafiles; i++)
	{
		if (!strcmp(m_source->datafile[i].conf.dirfile.field_code, m_buffer))
		{
			return ebex_data_copy_to_buffer(m_source, i, m_data, m_num);
		}
	}
	return 0;
}

size_t ebex_data_copy_to_buffer(data_source_t *m_source, size_t m_buffer, const double *m_data, size_t m_num)
{
	size_t retval = 0;

	if (m_num > m_source->buffer_size - m_source->buffer_produced) ebex_data_write_buffer(m_source);
	if (m_num > m_source->buffer_size) ebex_data_resize(m_source, 2 * m_num);

	for (retval = 0; retval < m_num; retval++)
	{
		m_source->databuffer[m_buffer][m_source->buffer_produced + retval] = m_data[retval];
	}

	return retval;
}

size_t ebex_data_copy_time_to_buffer(data_source_t *m_source, const double *m_data, size_t m_num)
{
	size_t retval = 0;

	if (m_num > m_source->buffer_size - m_source->buffer_produced) ebex_data_write_buffer(m_source);
	if (m_num > m_source->buffer_size) ebex_data_resize(m_source, 2 * m_num);

	for (retval = 0; retval < m_num; retval++)
	{
		m_source->timebuffer[m_source->buffer_produced + retval] = m_data[retval];
	}

	return retval;
}

bool ebex_data_set_parameters(data_file_t *m_file, const void *m_parameters)
{
	return process_data[m_file->type].set_parameters(m_file, m_parameters);
}

